W tym zadaniu będziemy szacować ceny domów.
import numpy as np
import pandas as pd
import xgboost as xgb
from xgboost.sklearn import XGBRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error as MSE
import lime
import lime.lime_tabular
import sklearn
from sklearn.ensemble import RandomForestRegressor
df = pd.read_csv('kc_house_data.csv')
df
| id | date | price | bedrooms | bathrooms | sqft_living | sqft_lot | floors | waterfront | view | ... | grade | sqft_above | sqft_basement | yr_built | yr_renovated | zipcode | lat | long | sqft_living15 | sqft_lot15 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7129300520 | 20141013T000000 | 221900.0 | 3 | 1.00 | 1180 | 5650 | 1.0 | 0 | 0 | ... | 7 | 1180 | 0 | 1955 | 0 | 98178 | 47.5112 | -122.257 | 1340 | 5650 |
| 1 | 6414100192 | 20141209T000000 | 538000.0 | 3 | 2.25 | 2570 | 7242 | 2.0 | 0 | 0 | ... | 7 | 2170 | 400 | 1951 | 1991 | 98125 | 47.7210 | -122.319 | 1690 | 7639 |
| 2 | 5631500400 | 20150225T000000 | 180000.0 | 2 | 1.00 | 770 | 10000 | 1.0 | 0 | 0 | ... | 6 | 770 | 0 | 1933 | 0 | 98028 | 47.7379 | -122.233 | 2720 | 8062 |
| 3 | 2487200875 | 20141209T000000 | 604000.0 | 4 | 3.00 | 1960 | 5000 | 1.0 | 0 | 0 | ... | 7 | 1050 | 910 | 1965 | 0 | 98136 | 47.5208 | -122.393 | 1360 | 5000 |
| 4 | 1954400510 | 20150218T000000 | 510000.0 | 3 | 2.00 | 1680 | 8080 | 1.0 | 0 | 0 | ... | 8 | 1680 | 0 | 1987 | 0 | 98074 | 47.6168 | -122.045 | 1800 | 7503 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 21608 | 263000018 | 20140521T000000 | 360000.0 | 3 | 2.50 | 1530 | 1131 | 3.0 | 0 | 0 | ... | 8 | 1530 | 0 | 2009 | 0 | 98103 | 47.6993 | -122.346 | 1530 | 1509 |
| 21609 | 6600060120 | 20150223T000000 | 400000.0 | 4 | 2.50 | 2310 | 5813 | 2.0 | 0 | 0 | ... | 8 | 2310 | 0 | 2014 | 0 | 98146 | 47.5107 | -122.362 | 1830 | 7200 |
| 21610 | 1523300141 | 20140623T000000 | 402101.0 | 2 | 0.75 | 1020 | 1350 | 2.0 | 0 | 0 | ... | 7 | 1020 | 0 | 2009 | 0 | 98144 | 47.5944 | -122.299 | 1020 | 2007 |
| 21611 | 291310100 | 20150116T000000 | 400000.0 | 3 | 2.50 | 1600 | 2388 | 2.0 | 0 | 0 | ... | 8 | 1600 | 0 | 2004 | 0 | 98027 | 47.5345 | -122.069 | 1410 | 1287 |
| 21612 | 1523300157 | 20141015T000000 | 325000.0 | 2 | 0.75 | 1020 | 1076 | 2.0 | 0 | 0 | ... | 7 | 1020 | 0 | 2008 | 0 | 98144 | 47.5941 | -122.299 | 1020 | 1357 |
21613 rows × 21 columns
Dane zawierają takie dane jak:
Możemy wyszczególnić w tych danych 4 kategorie:
Wynika stąd, że dane typu "id" powinny zostać usunięte z tego względu, że mogą powodować szum, z drugiej strony w id może być ukryta jakaś informacja, z której model mógłby wywnioskować cenę. Aby przygotować dane należy też udostępnić datę sprzedaży w bardziej przystępnym formacie.
df[['year', 'month', 'day']] = pd.DataFrame([ [int(x[0:4]), int(x[4:6]), int(x[6:8])] for x in df['date'].tolist() ])
df = df.drop(['date', 'id'], axis=1)
Y = df['price']
X = df.drop(['price'], axis=1)
df.groupby(['condition']).count()
| price | bedrooms | bathrooms | sqft_living | sqft_lot | floors | waterfront | view | grade | sqft_above | ... | yr_built | yr_renovated | zipcode | lat | long | sqft_living15 | sqft_lot15 | year | month | day | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| condition | |||||||||||||||||||||
| 1 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | ... | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 |
| 2 | 172 | 172 | 172 | 172 | 172 | 172 | 172 | 172 | 172 | 172 | ... | 172 | 172 | 172 | 172 | 172 | 172 | 172 | 172 | 172 | 172 |
| 3 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | ... | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 | 14031 |
| 4 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | ... | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 | 5679 |
| 5 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | ... | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 | 1701 |
5 rows × 21 columns
Xnp = X.to_numpy()
Ynp = Y.to_numpy()
train_X, test_X, train_Y, test_Y = train_test_split(Xnp, Ynp,
test_size = 0.3, random_state = 123)
rndForest = RandomForestRegressor(random_state=0, n_estimators=1000)
rndForest.fit(train_X, train_Y)
score = rndForest.score(train_X, train_Y)
print("Training score: ", score)
score = rndForest.score(test_X, test_Y)
print("Test score: ", score)
pred = rndForest.predict(test_X)
rmse = np.sqrt(MSE(test_Y, pred))
print("RMSE : % f" %(rmse))
Training score: 0.9828493662369712 Test score: 0.8807256473820063 RMSE : 127269.049649
test_Y[:2]
array([532500., 410000.])
rndForest.predict(test_X[:2])
array([633054.651, 521857.423])
explainer = lime.lime_tabular.LimeTabularExplainer(train_X,
feature_names=X.columns,
class_names=['price'],
verbose=True, mode='regression')
for i in range(2):
exp = explainer.explain_instance(test_X[i], rndForest.predict, num_features=8)
exp.show_in_notebook(show_table=True)
Intercept 739183.3914059246 Prediction_local [677998.85308265] Right: 633054.651
Intercept 913434.9095851758 Prediction_local [504441.31914628] Right: 521857.423
i = 0
for j in range(3):
exp = explainer.explain_instance(test_X[i], rndForest.predict, num_features=8)
exp.show_in_notebook(show_table=True)
Intercept 800847.0347211908 Prediction_local [655787.68023932] Right: 633054.651
Intercept 806123.1896437899 Prediction_local [634354.39840061] Right: 633054.651
Intercept 725682.1577039256 Prediction_local [653720.98538368] Right: 633054.651
i = 2508
for j in range(3):
exp = explainer.explain_instance(test_X[i], rndForest.predict, num_features=8)
exp.show_in_notebook(show_table=True)
Intercept 414050.54249061324 Prediction_local [1062172.54892981] Right: 1069058.808
Intercept 416835.4399141115 Prediction_local [1257338.65078289] Right: 1069058.808
Intercept 394273.09089666925 Prediction_local [1323188.40117372] Right: 1069058.808
Odp. 4 Dekompozycje LIME są stabilne, pomiędzy kilkoma uruchomieniami widać niewielkie różnice w wyjaśnieniach, lecz zbytnio od siebie nie odbiegają. Widać, że nasz model nauczył się, że dostęp do nabrzeża jest bardzo istotny przy wycenie domu oraz ogólny stan budynku.
xgb = XGBRegressor(colsample_bytree=0.7, learning_rate=0.06, max_depth=5, min_child_weight=6, n_estimators=700, nthread=1, objective='reg:squarederror', subsample=0.7)
xgb.fit(train_X, train_Y)
score = xgb.score(train_X, train_Y)
print("Training score: ", score)
score = xgb.score(test_X, test_Y)
print("Test score: ", score)
pred = xgb.predict(test_X)
rmse = np.sqrt(MSE(test_Y, pred))
print("RMSE : % f" %(rmse))
Training score: 0.9711698297228819 Test score: 0.905722369519171 RMSE : 113149.749407
for i in range(10):
exp = explainer.explain_instance(test_X[i], xgb.predict, num_features=8)
exp.show_in_notebook(show_table=True)
exp = explainer.explain_instance(test_X[i], rndForest.predict, num_features=8)
exp.show_in_notebook(show_table=True)
Intercept 1002735.3230309403 Prediction_local [600493.09736163] Right: 617686.7
Intercept 807646.4801584381 Prediction_local [658823.58264723] Right: 633054.651
Intercept 975764.9335415869 Prediction_local [447641.46635956] Right: 525400.25
Intercept 985887.1898063116 Prediction_local [493074.31003203] Right: 521857.423
Intercept 722898.7790766312 Prediction_local [977387.07915427] Right: 869056.5
Intercept 708714.5140881877 Prediction_local [1029692.50724078] Right: 890836.642
Intercept 783734.9900860798 Prediction_local [1005277.91231947] Right: 986680.4
Intercept 717072.8174435877 Prediction_local [980263.87618838] Right: 977896.355
Intercept 1080922.099259688 Prediction_local [304381.51963202] Right: 302756.4
Intercept 1038633.6508105842 Prediction_local [331469.567404] Right: 285091.049
Intercept 1033471.984884684 Prediction_local [467467.42776427] Right: 301077.2
Intercept 799557.6011496659 Prediction_local [354570.63487606] Right: 238258.141
Intercept 851200.3792432602 Prediction_local [860828.63981755] Right: 696768.8
Intercept 847362.0312884229 Prediction_local [802045.89705491] Right: 617916.082
Intercept 1048687.3915178354 Prediction_local [386827.68558178] Right: 419573.34
Intercept 1003781.1800090196 Prediction_local [367983.99433737] Right: 391979.9
Intercept 961076.4892628484 Prediction_local [572374.42975379] Right: 457880.12
Intercept 981111.0223067126 Prediction_local [638048.09955777] Right: 440373.072
Intercept 996418.6606816752 Prediction_local [549775.38920574] Right: 462333.66
Intercept 824476.0223869365 Prediction_local [558823.33579312] Right: 467267.582
i = 8
exp = explainer.explain_instance(test_X[i], xgb.predict, num_features=8)
exp.show_in_notebook(show_table=True)
exp = explainer.explain_instance(test_X[i], rndForest.predict, num_features=8)
exp.show_in_notebook(show_table=True)
Intercept 1019975.4907282477 Prediction_local [560986.41281683] Right: 457880.12
Intercept 803569.6671779978 Prediction_local [607395.54271191] Right: 440373.072
test_Y[8]
465000.0
Odp 5. W powyższym przykładzie atrubuty są różne dla różnych modeli. Cena jest bliższa prawdziwej dla XGBoost'a, co może znaczyć, że XGBoost potrafi się lepiej nauczyć przewidywać ceny domów.
Jak widać na powyższych wykresach wyjaśnienie nieznacznie się różni, ale wynika to z konstrukcji lime'a czyli losowania punktów w otoczeniu badanego punktu. Jednak najistotniejsze atrybuty za każdym razem mają zbliżony wpływ na wynik. Stąd można wnioskować, że LIME jest stabilny.
Po wytrenowaniu dwóch modeli lasu losowego i xgboost'a widać, że wyjaśnienie się różni. Najważniejszym atrybutem jest dostęp do nabrzeża, co w tym przypadku znaczy, że nie ma dostępu do nabrzeża (waterfront = 0). XGBoost jest znacznie bliżej poprawnego wyniku niż las losowy. Drugim najbardziej istotnym atrybutem według XGBoosta jest wielkość terenu przy domu, natomiast według lasu losowego większy wpływ na cenę ma widok, który w przypadku tego domu jest słaby i został oceniony na 0. Las losowy w ogóle nie wziął pod uwagę wielkości podwórka jako ważnego atrybutu (sqft_lot jest poza top8 atrybutów). Prawdopodobnie wynika to z faktu, że XGBoost jest znacznie lepszym modelem i potrafi bardziej zbliżyć się do poprawnego wyniku.